/*
 * ==========================================================================
 *
 *       Filename:  geomtest.cpp
 *
 *    Description:  to test the geometry part of VRHandSim project
 *
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  Weiyu Yi 
 *        Company:  
 *
 * ==========================================================================
 */

//---------------------------------------------------------------------------
//  Includes
//---------------------------------------------------------------------------

#include <math.h>
#include <string.h>
#include <iostream>
#include <vector>

#include <GL/gl.h>
#include <GL/glut.h>

#include <defEException.h>
#include <defEGeometry.h>
#include <defEGeomUtils.h>

#include <HandConfig.h>

#if CUDA_SUPPORTED == 1
#include <cuda_runtime.h>
#endif

#if TEXTURE_SUPPORTED == 1
#include <IL/ilut.h>
#endif // TEXTURE_SUPPORTED

#include <Hand.h>
#include <Loader.h>

using namespace vhand;

namespace
{

typedef enum _ProjectionModeE
{
	PERSPECTIVE,
	ORTHOGRAPHIC	
} ProjectionModeE;

typedef enum _ControlModeE
{
	MODELING,
	VIEWING
} ControlModeE;

// here declaration of the global variables.
Geometry * geo = NULL;
Hand * hand = NULL;
CoordSys * coordsys = NULL;
bool with_windows = true;

// render options
GLuint w, h;
ControlModeE ctrol = VIEWING;
ProjectionModeE proj = PERSPECTIVE;
GLenum fill_mode = GL_LINE;
GLdouble color[3] = { 0.9, 0.9, 0.9 };
Trackball tball;

GLdouble view_distance = 2.0;
Matrix4 moving_matrix;
Matrix4 viewing_matrix;

// debugging
int g_times = 0;

} // namespace local

void testcase( int *phase )
{
	printf("\nphase %d\n", *phase );

	switch ( *phase )
	{
		case 0: // Read Bones from obj file and draw it out
		{
			if ( geo != NULL ) 
			{
				delete geo;
			}
			if ( hand != NULL ) 
			{
				delete hand;
			}

            try
            {
                //Loader *loader = new Loader( "HandAllData.cfg" );
                Loader *loader = new Loader( "HandAllData.cfg_original" );
                hand = new Hand();
                loader->loadHand( hand );

                hand->setSkinTransparency( 0.6 );
                hand->setSpheresTransparency( 0.8 );
                hand->editSkinGeom()->setFillMode( GL_LINE );
                hand->initDevice();

                coordsys = new CoordSys( 2.0 );
                coordsys->initGraphicsResource();
            }
            catch( XError &x )
            {
                x.print();
                exit( 0 );
            }

			break;
		}
	}
	(*phase) ++;
}

void initGlut( int argc, char *argv[] )
{
	glutInit( &argc, argv );
	glutInitDisplayMode ( GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB );

	glutInitWindowSize( 700, 700 ); 
	glutInitWindowPosition( 50, 50 );
	glutCreateWindow( argv[0] );

}

void initGL(void) 
{
	glClearColor( 0.0, 0.0, 0.0, 0.0 );
	glShadeModel( GL_SMOOTH );
	glEnable( GL_DEPTH_TEST );

    //enable lighting
    glEnable( GL_NORMALIZE );
    glEnable( GL_LIGHTING );
    glEnable( GL_LIGHT0 );

#if TEXTURE_SUPPORTED == 1
    //enable texture
    glEnable( GL_TEXTURE_2D );
#endif // TEXTURE_SUPPORT

    //enable translucent
    glEnable( GL_BLEND );
    glBlendFunc( GL_SRC_ALPHA,
                 GL_ONE_MINUS_SRC_ALPHA );

#if CUDA_SUPPORTED == 1
    cudaSetDevice( 0 );
    cudaGLSetGLDevice( 0 );

    cudaError err = cudaThreadSynchronize();
    if ( cudaSuccess != err)
    {
        fprintf(stderr, "Cuda error in file '%s' in line %i : %s.\n", __FILE__, __LINE__, cudaGetErrorString(err) );
    }
#endif

}

void reshape( int width, int height )
{
	w = width;
	h = height;
	glViewport( 0, 0, (GLsizei) w, (GLsizei) h );

	tball.setViewport( width, height );
}

void display(void)
{
    try
    {
        // trackball
        if ( ctrol == VIEWING )
        {
            if ( tball.getTrackMode() == TRACK_ROTATE )
            {
                float angle;
                float axis[3];
                tball.getRotation( axis, angle );
                Vector3 ax( axis[0], axis[1], axis[2] );
                Matrix4 m = Matrix4::rotation( angle, ax );
                viewing_matrix = m * viewing_matrix;
            }
            else if ( tball.getTrackMode() == TRACK_ZOOM )
            {
                float zoom;
                tball.getZoom( zoom );
                Vector3 zm( zoom+1 );
                Matrix4 m = Matrix4::scale( zm );
                viewing_matrix = m * viewing_matrix;
            }
        }

        if ( ctrol == MODELING )
        {
            if ( tball.getTrackMode() == TRACK_ROTATE )
            {
                float axis[3];
                float angle;
                tball.getRotation( axis, angle );
                //printf( "----------------rotate times: %d-------------------\n", g_times++ );
                
                //Matrix4 m = Matrix4::rotation( angle, normalize( Vector3( axis[0], axis[1], axis[2] ) ) );
                Matrix4 m = Matrix4::rotation( -0.05, normalize( Vector3( 1.0, 0.0, 0.0 ) ) );
                hand->moveLimb( "rIndex3", m );
                //hand->moveLimb( "rThumb1", m );

                //Matrix4 m3 = Matrix4::rotation( angle, normalize( Vector3( axis[0], axis[1], axis[2] ) ) );
                //hand->moveLimb( "rHand", m3 );

                //Matrix4 m4 = Matrix4::rotation( angle, normalize( Vector3( axis[0], axis[1], axis[2] ) ) );
                //Matrix4 m4 = Matrix4::rotation( 0.01, normalize( Vector3( 0.0, 1.0, 0.0 ) ) );
                //hand->moveLimb( "rForearm", m4 );
                hand->deformation();
                hand->updateGeometry();
            }
            else if ( tball.getTrackMode() == TRACK_ZOOM )
            {
                float trans;
                tball.getZoom( trans );
                Matrix4 m = Matrix4::translation( Vector3( trans/10.0, 0.0, 0.0 ) );
                moving_matrix = m * moving_matrix;
            }
        }


        // display begins
        glMatrixMode( GL_PROJECTION );
        glLoadIdentity();

        GLdouble range=2.0;
        switch( proj )
        {
            case ORTHOGRAPHIC:
                // orthographic projection
                if (w <= h)
                    glOrtho( -1*range, range, -1*range*(GLdouble)h/(GLdouble)w,
                            range*(GLdouble)h/(GLdouble)w, 0.1, 10.0 );
                else
                    glOrtho( -1*range*(GLdouble)w/(GLdouble)h,
                            range*(GLdouble)w/(GLdouble)h, -1*range, range, 0.1, 10.0 );
                break;
            case PERSPECTIVE:
                // perspective projection
                gluPerspective( 60, (GLdouble)w/(GLdouble)h, 0.1, 10.0 );
                break;
            default:
                fprintf( stderr, "No such projection mode!\n" );
                break;
        }

        glMatrixMode( GL_MODELVIEW );
        glLoadIdentity();
        glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

        // View Transformation
        glTranslatef( 0.0, 0.0, -view_distance );

        // define one stationary directionary lightsource
        float light_position[4] = { 0.0, 0.0, 1.0, 0.0 };
        glLightfv( GL_LIGHT0, GL_POSITION, light_position );

        // begin rendering the scene
        GLdouble mat[16];
        for ( unsigned int i=0; i<4; i++ )
            for ( unsigned int j=0; j<4; j++ )
                mat[ i*4+j ] = viewing_matrix[i][j];
        glMultMatrixd( mat );

        // draw geometry
        if ( hand != NULL )
        {
            hand->draw();
        }
        if ( coordsys != NULL )
        {
            coordsys->draw();
        }

    }
    catch( XError &x )
    {
        x.print();
    }

	//
	glutSwapBuffers();
}

void mouseClick( int button, int state, int x, int y )
{
    //fprintf( stdout, "%s : %s\n", __FILE__, __FUNCTION__ );

	tball.mouseClick( button, state, x, y );
}

void mouseMoving( int x, int y )
{
    //fprintf( stdout, "%s : %s\n", __FILE__, __FUNCTION__ );

	tball.mouseMoving( x, y );
	glutPostRedisplay();
}

void keyboard(unsigned char key, int x, int y)
{
	static int phase = 0;
	switch (key) {
		case 27:
			if ( geo != NULL )
				delete geo;
			exit(0);
			break;
		case 'm':
		case 'M':
			if ( ctrol == VIEWING )
				ctrol = MODELING;
			else
			if ( ctrol == MODELING )
				ctrol = VIEWING;

			glutPostRedisplay();
			break;
		case ' ':
			testcase( &phase );

			glutPostRedisplay();
			break;
		case 'p':
		case 'P':
			if ( proj == PERSPECTIVE )
				proj = ORTHOGRAPHIC;
			else
			if ( proj == ORTHOGRAPHIC )
				proj = PERSPECTIVE;

			glutPostRedisplay();
			break;
		case 'i':
		case 'I':
			if ( fill_mode == GL_LINE )
				fill_mode = GL_FILL;
			else
			if ( fill_mode == GL_FILL )
				fill_mode = GL_POINT;
			else
			if ( fill_mode == GL_POINT )
				fill_mode = GL_LINE;

			glutPostRedisplay();
			break;
        case 's':
        case 'S':
            if ( hand->drawSkin == true )
                hand->drawSkin = false;
            else
                hand->drawSkin = true;

			glutPostRedisplay();
			break;
        case 'd':
        case 'D':
            if ( hand->drawSpheres == true )
                hand->drawSpheres = false;
            else
                hand->drawSpheres = true;

			glutPostRedisplay();
			break;
        case 'v':
        case 'V':
            if ( hand->drawVolume == true )
                hand->drawVolume = false;
            else
                hand->drawVolume = true;

			glutPostRedisplay();
			break;
        case 'b':
        case 'B':
            if ( hand->drawBones == true )
                hand->drawBones = false;
            else
                hand->drawBones = true;

			glutPostRedisplay();
			break;
		default:
			break;
	}
}

/***************************************************************************\
 *                              Main                                       *
\***************************************************************************/

void commandlineerror( char *cmd, char *parm )
{
	if ( cmd )
		fprintf(stderr, "Offending option: %s\n", cmd );
	if ( parm )
		fprintf(stderr, "with first parameter: %s\n", parm );

	fprintf(stderr, "\n\nUsage: geometry options ...\n"
	"Options:\n"
	"-h          show help\n"
	"Keys: (interactive version)\n"
	"i           polygon mode (wireframe, dots, filled)\n"
	"space       go to the next test case\n"
	"ESC         quit\n"
	"\n");

	if ( cmd )
		exit(-1);				// problem occured
	else
		exit(0);				// was option -h
}


void parsecommandline( int argc, char *argv[] )
{
	/* valid option characters; last char MUST be 0 ! */
	char optionchar[] =   { 'h', 'w', '\0' };
	int musthaveparam[] = {  0 ,  0  };
	int mhp[256];
	int isopt[256];
	char optchar;


	unsigned int nopts = strlen(optionchar);
	if ( nopts > 50 )
	{
		fprintf(stderr, "\n\nparsecommandline: the option-chars string "
				"seems to be\nVERY long (%d bytes) !\n\n", nopts );
		exit(-1);
	}

	std::fill_n( isopt, 256, 0 );
	std::fill_n( mhp, 256, 0 );
	for ( unsigned int i = 0; i < nopts; i ++ )
	{
		if ( isopt[static_cast<int>(optionchar[i])] )
		{
			fprintf(stderr, "\n\nparsecommandline: Bug: option character '%c'"
					" is specified twice in the\n"
							"option character array !\n\n", optionchar[i] );
			exit(-1);
		}
		isopt[ static_cast<int>(optionchar[i]) ] = 1;
		mhp[ static_cast<int>(optionchar[i])] = musthaveparam[i];
	}

	++argv; --argc;
	while ( argc > 0 )
	{
		if ( argv[0][0] == '-' )
		{
			optchar = argv[0][1];

			if ( ! isopt[static_cast<int>(optchar)] )
			{
				fprintf(stderr, "\nIs not a valid command line option\n");
				commandlineerror( argv[0], NULL );
			}
			for ( int i = 0; i < mhp[static_cast<int>(optchar)]; i ++ )
				if ( ! argv[1+i] || argv[1+i][0] == '-' )
				{
					fprintf(stderr, "\nCommand line option -%c must "
							"have %d parameter(s)\n",
							argv[0][1], mhp[static_cast<int>(optchar)] );
					commandlineerror( argv[0], NULL );
					argv += 1 + i;
					argc -= 1 + i;
					continue;
				}

			switch ( optchar )
			{
				case 'h': commandlineerror( NULL, NULL);  break;
				default: fprintf(stderr, "\nBug in parsecommandline !\n");
						 commandlineerror( argv[0], NULL );
			}

			argv += 1 + mhp[static_cast<int>(optchar)];
			argc -= 1 + mhp[static_cast<int>(optchar)];
		}
		else
		{
			/* command line arg doesn't start with '-' */
			fprintf(stderr, "\nThis is not a valid command line option\n");
			commandlineerror( argv[0], NULL );
			/* or, load file instead .. */
		}
	}
}


int main(int argc, char** argv)
{
	// parse command line options
	parsecommandline( argc, argv );

	// preparing data
	viewing_matrix = Matrix4::identity();
	moving_matrix = Matrix4::identity();

    // init
    initGlut( argc, argv );
	initGL();
#if TEXTURE_SUPPORTED == 1
    ilInit();
#endif

	// register callback to glut
	glutDisplayFunc( display ); 
	glutReshapeFunc( reshape );

	glutMouseFunc( mouseClick );
	glutMotionFunc( mouseMoving );
	glutKeyboardFunc( keyboard );

    glutMainLoop();

	return 0;
}
